/**
 * Copyright (c) 2009, Robert Schuster
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 *
 * - Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer in the documentation and/or other
 *   materials provided with the distribution.
 * - Neither the name of Robert Schuster nor the names of his
 *   contributors may be used to endorse or promote products
 *   derived from this software without specific prior written
 *   permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* 
 * File:   Executor.hpp
 * Author: Robert Schuster
 *
 * The Executor is a simplification of the OpenCL-API
 *
 * Created on 3. September 2009, 14:03
 */

#ifndef _EXECUTOR_HPP
#define	_EXECUTOR_HPP

#include <iostream>
#include <Device.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/ptr_container/ptr_map.hpp>
#include <boost/ptr_container/nullable.hpp>
#include <OpenCL/opencl.h>

using namespace std;

namespace CLcalc {

class Executor {
public:
    Executor(Device* device);
    Executor(const Executor& orig);
    virtual ~Executor();

    /**
     * Add a Program and build the kernel, if already compiled, use the compiled
     * Programm
     * @return the ProgID used in Execute and setArgument
     */
    int addProgFromSource(const char* source, const char* name) throw (Error);

    /**
     * Copy data to the Device, or create a Buffer on the device for the
     * use of a kernel
     * @param data      a pointer to the Data array
     * @param data_size the size of the data-array in bytes! sizeof(datatyp)*length
     * @param data_dir  the direction CL_MEM_...
     * @return the DataID of the Buffer on the Device
     */
    int addData(void* data, size_t data_size, int data_dir) throw (Error);

    /**
     * @param data      a pointer to the Data array where the data sould be placed in
     * @param data_size the size of the data-array in bytes! sizeof(datatyp)*length
     *
     */
    void getData(int DataID, void* data, size_t data_size) throw (Error);

    /**
     * @param DataID    the ID of this data Object inside the Executor
     */
    void releaseData(int DataID) throw (Error);

    /**
     * The functions returns the Host-Pointer to the data-object
     * @param DataID    the ID of this data Object inside the Executor
     */
    void* getDataHostPtr(int DataID) throw (Error);

    /**
     * get the direction of the data object
     * @param DataID        the ID of this data Object inside the Executor
     * @return              CL_MEM_... the value used while creating the object
     */
    long getDataDir(int DataID) throw (Error);

    /**
     * Set a Data Object as Argument
     * @param ProgID    the ID of the Program returned by the addProg... function
     * @param arg_pos   the position of the argument in the kernel declaration
     * @param DataID    the ID of the buffer on the device returned by addData
     */
    void setArgument(int ProgID, int arg_pos, int DataID) throw (Error);

    /**
     * Set a const int argument
     * @param ProgID    the ID of the Program returned by the addProg... function
     * @param arg_pos   the position of the argument in the kernel declaration
     * @param argument  the value of the argument, this is not a pointer the value is copied
     */
    void setConstIntArgument(int ProgID, int arg_pos, int argument) throw (Error);

    /**
     * Execute the Kernel with the Index 0 to MaxIndex
     * @param ProgID    the ID of the Program returned by the addProg... function
     * @param MaxIndex  the number of intances that should be executed
     * @return 0 on success
     */
    void Execute(int ProgID, int MaxIndex) throw (Error);

    /**
     * Execute the Kernel with the Index 0 to MaxIndex with a specified work group size
     * @param ProgID    the ID of the Program returned by the addProg... function
     * @param MaxIndex  the number of intances that should be executed
     * @param wg_size   the size of the Work-Group 
     * @return 0 on success
     */
    void Execute(int ProgID, int MaxIndex, int wg_size) throw (Error);

    /**
     * Returns a pointer to the device refered to by the DeviceID
     */
    Device* getDevice(int DeviceID) throw (Error);

    /**
     * wait for all commands to complete
     */
    void waitForAll(int DeviceID) throw (Error);

    size_t getDatatypSizeFromName(const char* templatetype);

    /**
     * Interface to the Buildin functions---------------------------------------------------------
     */

    /*
     * Calculate the sum of each element of 2 arrays
     * @param arg1      the 1st argument array
     * @param arg2      the 2nd argument array
     * @param result    the result array
     * @param length    the length of each dimension
     * @param dims      the number of dimension
     * @param basic_typ     the type of basic arithmetic operation to use
     */
    void BuildIn_basic(void* arg1, void* arg2, void* result, int* length, int dims, const char* fname, const char* tname) throw (Error);

    /*
     * Calculate the sum of each element of 2 arrays
     * @param arg1ID    the ID of the 1st argument array
     * @param arg2ID    the ID of the 2nd argument array
     * @param result    the ID of the result array
     * @param length    the length of each dimension
     * @param dims      the number of dimension
     * @param basic_typ     the type of basic arithmetic operation to use
     */
    void BuildIn_basic_execOnly(int arg1ID, int arg2ID, int resultID, int* length, int dims, const char* fname, const char* tname) throw (Error);

    /**
     * Use one of the OpenCL intrinsic functions which have one argument
     * for example output[i] = sqrt(input[i])
     * @param input     the input array
     * @param output    the output array
     * @param length    the length of each dimension
     * @param dims      the number of dimensions
     * @param fname     the function to use, example: "sqrt"
     * @param tname     the type to use, example: "float"
     */
    void BuildIn_CLintrinsics1arg(void* input, void* output, int* length, int dims, const char* fname, const char* tname) throw (Error);

    /**
     * Use one of the OpenCL intrinsic functions which have one argument
     * for example output[i] = sqrt(input[i])
     * @param input     the ID of the input array
     * @param output    the ID of the output array
     * @param length    the length of each dimension
     * @param dims      the number of dimensions
     * @param fname     the function to use, example: "sqrt"
     * @param tname     the type to use, example: "float"
     */
    void BuildIn_CLintrinsics1arg_execOnly(int inputID, int outputID, int* length, int dims, const char* fname, const char* tname) throw (Error);

private:

    /*
     * All Devices in this Executor
     */
    boost::ptr_vector<Device> devices;

    /*
     * All Programs in this Executor
     */
    boost::ptr_vector<CompiledProgram> Programs;

    /*
     * All Memory-Objects in this Executor
     */
    boost::ptr_vector< boost::nullable<cl_mem> > Memorys;

    /*
     * The Executor has it's own context
     */
    cl_context* Context;

    /*
     * A Command Queue for each Device is created
     */
    cl_command_queue* Queue;

    /*
     * Init the Context and the Queue
     */
    void Init_Context() throw (Error);

    /*
     * Prepare the code for compilation
     * and process the templates
     */
    void Preprocessor(string &source, string &name) throw (Error);
};

} // end of namespace
#endif	/* _EXECUTOR_HPP */

